//use std::{fs, io::Write};

use std::sync::MutexGuard;

use crate::*;

use capstone::{
    arch::arm::ArchMode as armArchMode, arch::arm64::ArchMode as aarch64ArchMode,
    arch::riscv::ArchMode as riscvArchMode, prelude::*, Endian,
};
use probe_rs::Architecture;

pub fn update_pc(ui_io: &UiIo, pc: u64) {
    ui_io.set_PC(pc as i32);
    ui_io.set_PC_str(format!("0x{:08X}", pc).into());
}

pub const RISCV_CSRS: &[(&str, u16)] = &[
    ("marchid", 0xf12),
    ("mimpid", 0xf13),
    ("mhartid", 0xf14),
    ("misa", 0x301),
    ("mtvec", 0x305),
    ("mscratch", 0x340),
    ("mepc", 0x341),
    ("mcause", 0x342),
    ("mtval", 0x343),
    ("mstatus", 0x300),
    ("dcsr", 0x7b0),
    ("dpc", 0x7b1),
    ("dscratch0", 0x7b2),
    ("dscratch1", 0x7b3),
];
pub const QINGKE_CSRS: &[(&str, u16)] = &[
    // Qingke-RISCV CSR extensions
    ("gintenr", 0x800),
    ("intsyscr", 0x804),
    ("corecfgr", 0xbc0),
];

fn riscv_csr(core: &mut Core) -> Vec<(String, String)> {
    //(name, value)
    let mut csr_list: Vec<(String, String)> = Vec::new();

    for (name, addr) in RISCV_CSRS {
        if let Ok(value) = core.read_core_reg::<u32>(*addr) {
            csr_list.push((format!("{:10}", name), format!("{:08X}", value)))
        }
    }

    if let Ok(value) = core.read_core_reg::<u32>(0xf12) {
        if (value & 0x7FFF0000) == ((0x17 << 26) | (0x03 << 21) | (0x08 << 16)) {
            for (name, addr) in QINGKE_CSRS {
                if let Ok(value) = core.read_core_reg::<u32>(*addr) {
                    csr_list.push((format!("{:10}", name), format!("{:08X}", value)))
                }
            }
        }
    }

    return csr_list;
}

pub fn update_mcu_core(core: &mut Core, ui_io: &UiIo, pc: u64) {
    let mut mcu_core = ui_io.get_mcu_core();
    update_pc(ui_io, pc);

    let mut core_regs: Vec<CoreReg> = Vec::new();
    let register_file = core.registers();

    for register in register_file.core_registers() {
        let value: RegisterValue = core.read_core_reg(register).unwrap();
        let value_str = format!("{}", value);

        //println!("{:10}: {:#}", register.name(), value);
        let reg = CoreReg {
            name: format!("{:7}", register).into(),
            value: value_str.into(),
        };
        core_regs.push(reg);
    }
    mcu_core.Core = Rc::new(VecModel::from(core_regs)).into();

    let mut bank_regs: Vec<CoreReg> = Vec::new();
    if core.architecture() == Architecture::Riscv {
        for (name, value) in riscv_csr(core) {
            let reg = CoreReg {
                name: name.into(),
                value: value.into(),
            };
            bank_regs.push(reg);
        }
    } else {
        if let Some(msp) = register_file.msp() {
            let value: RegisterValue = core.read_core_reg(msp).unwrap();

            //println!("{:10}: {:#}", msp.name(), value);
            let reg = CoreReg {
                name: msp.name().into(),
                value: format!("{}", value).into(),
            };
            bank_regs.push(reg);
        }
        if let Some(psp) = register_file.psp() {
            let value: RegisterValue = core.read_core_reg(psp).unwrap();
            //println!("{:10}: {:#}", psp.name(), value);
            let reg = CoreReg {
                name: psp.name().into(),
                value: format!("{}", value).into(),
            };
            bank_regs.push(reg);
        }
    }
    mcu_core.Banked = Rc::new(VecModel::from(bank_regs)).into();

    ui_io.set_mcu_core(mcu_core);
}

pub fn dissassemble(
    instruction_set: InstructionSet,
    code: &[u8],
    addr: u64,
) -> Vec<DissassembleLine> {
    let min_inst_size = instruction_set.get_minimum_instruction_size() as usize;
    let cs = match instruction_set {
        InstructionSet::Thumb2 => Capstone::new()
            .arm()
            .mode(armArchMode::Thumb)
            .endian(Endian::Little)
            .build(),
        InstructionSet::A32 => {
            // We need to inspect the CPSR to determine what mode this is opearting in
            Capstone::new()
                .arm()
                .mode(armArchMode::Arm)
                .endian(Endian::Little)
                .build()
        }
        InstructionSet::A64 => {
            // We need to inspect the CPSR to determine what mode this is opearting in
            Capstone::new()
                .arm64()
                .mode(aarch64ArchMode::Arm)
                .endian(Endian::Little)
                .build()
        }
        InstructionSet::RV32 => Capstone::new()
            .riscv()
            .mode(riscvArchMode::RiscV32)
            .endian(Endian::Little)
            .build(),
        InstructionSet::RV32C => Capstone::new()
            .riscv()
            .mode(riscvArchMode::RiscV32)
            .endian(Endian::Little)
            .extra_mode(std::iter::once(
                capstone::arch::riscv::ArchExtraMode::RiscVC,
            ))
            .build(),
        InstructionSet::Xtensa => Err(capstone::Error::UnsupportedArch),
    }
    .map_err(|err| anyhow!("Error creating capstone: {:?}", err))
    .unwrap();

    let mut next_addr: u64 = addr;
    let mut offset: u64 = 0;
    let mut dissassemble: Vec<DissassembleLine> = Vec::new();
    loop {
        // Attempt to dissassemble
        match cs.disasm_all(&code[offset as usize..], addr + offset) {
            Ok(instructions) => {
                for i in instructions.iter() {
                    let mut asm_str: String = String::new();

                    asm_str.push_str(&format!("0x{:08X}: ", i.address()));
                    let mut bytes_str: String = String::new();
                    for b in i.bytes() {
                        bytes_str.push_str(&format!("{:02X}", b));
                    }
                    if i.bytes().len() == 2 {
                        bytes_str.push(' ');
                        for b in i.bytes() {
                            bytes_str.push(if b.is_ascii_graphic() {
                                char::from(*b)
                            } else {
                                ' '
                            });
                        }
                    }
                    asm_str.push_str(&format!("{:8}  ", bytes_str));
                    if let Some(mnemonic) = i.mnemonic() {
                        asm_str.push_str(&format!("{:7}", mnemonic));
                        if let Some(op_str) = i.op_str() {
                            asm_str.push_str(&format!("{}", op_str));
                        }
                    }

                    let disasm_line = DissassembleLine {
                        address: i.address() as i32,
                        assemble: asm_str.into(),
                        breakpoint: false,
                    };
                    dissassemble.push(disasm_line);
                    next_addr = i.address() + i.bytes().len() as u64;
                    offset += i.bytes().len() as u64;
                }
            }
            Err(e) => {
                println!("Error disassembling instructions: {e}");
            }
        }

        if offset < (code.len() - min_inst_size) as u64 {
            let mut raw_str: String = String::new();

            raw_str.push_str(&format!("{:#X}: ", next_addr));

            let mut bytes_str: String = String::new();
            let mut chars_str: String = " ".to_string();
            for _ in 0..min_inst_size {
                let one_byte = code[offset as usize];
                bytes_str.push_str(&format!("{:02X}", one_byte));
                chars_str.push(if one_byte.is_ascii_graphic() {
                    char::from(one_byte)
                } else {
                    ' '
                });
                offset += 1;
            }
            raw_str.push_str(&format!(
                "{:8} {:4} ; const/padding data",
                bytes_str, chars_str
            ));

            //println!("next_addr:0x{:08X}, offset:0x{:08X}", next_addr, offset);

            let disasm_line = DissassembleLine {
                address: (next_addr & 0xFFFFFFFF) as i32,
                assemble: raw_str.into(),
                breakpoint: false,
            };
            next_addr += min_inst_size as u64;
            dissassemble.push(disasm_line);
        } else {
            break;
        }
    }

    dissassemble
}

pub fn code_dissassemble(
    core: &mut Core,
    start: u64,
    size: usize,
) -> Result<Vec<DissassembleLine>, probe_rs::Error> {
    let mut code: Vec<u8> = Vec::new();

    code.resize(size, 0xFF);
    core.read(start, &mut code)?;

    // if let Ok(mut file) = fs::OpenOptions::new().write(true)
    //                                             .create(true)
    //                                             .append(false)
    //                                             .open("code.bin") {
    //     file.write_all(&code).ok();
    // }

    let instruction_set = core.instruction_set().unwrap();
    let diss_asm = dissassemble(instruction_set, &code, start);

    return Ok(diss_asm);
}

pub fn make_pc_line_visible(
    app: &mut MutexGuard<App>,
    ui_io: &UiIo,
    pc: u64,
    disp_lines_num: usize,
) {
    if let Some(flash_range) = &app.flash_range {
        if flash_range.contains(&pc) {
            if let Some((diss_asm, range)) = &mut app.dissassemble_lines {
                let mut start_line = 0;
                for line in &mut *diss_asm {
                    if pc as i32 == line.address {
                        break;
                    }
                    start_line += 1;
                }

                if start_line > 10 {
                    start_line -= 10;
                } else {
                    start_line = 0;
                }
                range.start = start_line as usize;
                range.end = range.start + disp_lines_num;
                if range.end > diss_asm.len() {
                    range.end = diss_asm.len();
                }

                ui_io.set_assemble_line_current(range.start as i32);
                let mut diss_asm_slice: Vec<DissassembleLine> = Vec::new();
                diss_asm_slice.extend_from_slice(&diss_asm[range.start..range.end]);
                ui_io.set_dissassemble_line(Rc::new(VecModel::from(diss_asm_slice)).into());
            }
        }
    }
}
